﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BeanboxSoftware.BeanProxy.Recording
{
  public class MethodRule
  {
    public bool IsSatisfied { get { return SatisfyCount == RepeatCount; } }
    public bool IsUndefined { get { return ExpectedCall.ExpectedArgs == null; } }

    public MulticastDelegate Callback { get { return ExpectedCall.ExpectedReturn.Callback; } }
    public ExpectedCallAccess ExpectedCall { get; protected set; }
    public int RepeatCount { get; protected set; }
    public int SatisfyCount { get; protected set; }

    public void AssertDefined(string prefix)
    {
      if (IsUndefined)
      {
        throw new InvalidOperationException(
          prefix + ":\r\n" +
          "You left a rule undefined. You must specific what method will" +
          " be called on each rule you make. In other words, calling Listen()" +
          " is not enough, but you must also call Liste().For() as the call" +
          " to For() defines the rules. Setting Then, in Listen().For().Then, is optional.");
      }
    }

    public void AssertSatisfied(string prefix)
    {
      AssertDefined(prefix);

      if (!IsSatisfied)
      {
        throw new InvalidOperationException(
          prefix + ":\r\n" +
          string.Format("You left a rule unsatisfied. You said [{0}] would be called {1} time but was called {2} times." +
          " Expected input [{3}]." ,
          ExpectedCall.MethodConfig.Method.Name,
          RepeatCount,
          SatisfyCount,
          ExpectedCall.ExpectedArgsList
          ));
      }
    }

    public virtual bool TryToSatisfy(MethodConfig mc, object[] input)
    {
      AssertDefined("Method Invocation failed");

      if (IsSatisfied) //a rule cannot satisfy again
      {
        return false;
      }

      if (ExpectedCall.MethodConfig != mc)
      {
        return false;
      }

      if (input == null)
      {
        input = new object[] { null };
      }

      if (input.Length != ExpectedCall.ExpectedArgs.Length)
      {
        return false;
      }

      for (int i = 0; i < input.Length; i++)
      {
        if (input[i] == null ^ ExpectedCall.ExpectedArgs[i] == null)
        {
          return false;
        }

        if (input[i] == null)
        {
          continue;
        }

        if (!input[i].Equals(ExpectedCall.ExpectedArgs[i]))
        {
          return false;
        }
      }

      SatisfyCount++;
      return true;
    }

    public MethodRule(int repeatCount, ExpectedCallAccess ec)
    {
      RepeatCount = repeatCount;
      ExpectedCall = ec;
    }
  }
}
